package ar.uba.fi.RSACryptoFiuba.model.encrypt;

import java.math.BigInteger;

import org.apache.commons.lang3.StringUtils;

import ar.uba.fi.RSACryptoFiuba.model.InvalidValueException;
import ar.uba.fi.RSACryptoFiuba.model.RSACalculator;
import ar.uba.fi.RSACryptoFiuba.model.RSAMessage;
import ar.uba.fi.RSACryptoFiuba.model.RSAStep;

public class EncryptStep extends RSAStep {
	
	private int position;

	public EncryptStep(int position) {
		super();
		this.position = position;
	}
		
	@Override
	public void execute(RSAMessage message) {
		RSAEncryptMessage encryptMessage = (RSAEncryptMessage) message;
		String cWithLeadingZeros = getCWithLeadingZeros(encryptMessage);		
		updateEncryptMessage(encryptMessage, cWithLeadingZeros);
		
		encryptMessage.update();
	}

	@Override
	public void undo(RSAMessage message) {
		RSAEncryptMessage encryptMessage = (RSAEncryptMessage) message;
		int nKeylength = encryptMessage.getNKey().length();
		String encryptedText = encryptMessage.getEncryptedText();
		String numericalRepresentation = encryptMessage.getNumericalRepresentation(); 		
		
		String newEncryptedText = encryptedText.substring(0, encryptedText.length() - nKeylength);
		encryptMessage.setEncryptedText(newEncryptedText);
		
		String newActualC = (!newEncryptedText.isEmpty()) ? encryptedText.substring(newEncryptedText.length() - nKeylength, newEncryptedText.length()) : "";
		encryptMessage.setActualC(newActualC);
		
		String newRemainNumericalRepresentation = numericalRepresentation.substring(position*(nKeylength-1), numericalRepresentation.length()); 
		encryptMessage.setRemainNumericalRepresentation(newRemainNumericalRepresentation);
		
		String newProcessedNumericalRepresentation = numericalRepresentation.substring(0, position*(nKeylength-1));
		encryptMessage.setProcessedNumericalRepresentation(newProcessedNumericalRepresentation);
		
		String newActualM = (newProcessedNumericalRepresentation.length() > (nKeylength -1)) ? numericalRepresentation.substring(newProcessedNumericalRepresentation.length() - (nKeylength -1), newProcessedNumericalRepresentation.length()) : encryptMessage.getProcessedNumericalRepresentation();
		encryptMessage.setActualM(newActualM);
		
		encryptMessage.update();
	}

	@Override
	public void test(RSAMessage message) {
		RSAEncryptMessage encryptMessage = (RSAEncryptMessage) message;
		String element = encryptMessage.getElementToEvaluate();
		String cWithLeadingZeros = getCWithLeadingZeros(encryptMessage);
		boolean isValid = cWithLeadingZeros.equals(element);
		if(!isValid){
			throw new InvalidValueException("El valor c debe ser igual a m^e mod n.");
		}
		updateEncryptMessage(encryptMessage, element);
		
		encryptMessage.update();
	}
	
	private String getCWithLeadingZeros(RSAEncryptMessage encryptMessage){
		int l = encryptMessage.getNKey().length();
		String remainNumericalRepresentation = encryptMessage.getRemainNumericalRepresentation();
		if(remainNumericalRepresentation.length()<l-1){
			remainNumericalRepresentation += repeat("0", l -1 - remainNumericalRepresentation.length());
		}
		String m = remainNumericalRepresentation.substring(0, l-1);
		BigInteger c = RSACalculator.exp(new BigInteger(m), Integer.valueOf(encryptMessage.getEKey()));
		c = RSACalculator.mod(c, new BigInteger(encryptMessage.getNKey()));
		return completeWithLeadingZeros(c, l);
	}
	
	private static String repeat(String str, int times) {
		return new String(new char[times]).replace("\0", str);
	}
	
	private String completeWithLeadingZeros(BigInteger number, int length) {
		return StringUtils.leftPad(number.toString(), length, "0");
	}
	
	private void updateEncryptMessage(RSAEncryptMessage encryptMessage,	String cWithLeadingZeros) {
		String remainNumericalRepresentation = encryptMessage.getRemainNumericalRepresentation();
		int l = encryptMessage.getNKey().length();
		String newRemainNumericalRepresentation = (remainNumericalRepresentation.length() >= l-1) ? remainNumericalRepresentation.substring(l-1, remainNumericalRepresentation.length()) : "";
		String newProcessesNumericalRepresentation= (remainNumericalRepresentation.length() >= l-1) ? remainNumericalRepresentation.substring(0, l-1) : remainNumericalRepresentation;
		
		String newActualM = (remainNumericalRepresentation.length()<l-1) ? remainNumericalRepresentation :	remainNumericalRepresentation.substring(0, l-1);
		encryptMessage.setActualM(newActualM);
		
		encryptMessage.setProcessedNumericalRepresentation(encryptMessage.getProcessedNumericalRepresentation() + newProcessesNumericalRepresentation);
		encryptMessage.setRemainNumericalRepresentation(newRemainNumericalRepresentation);
		encryptMessage.setActualC(cWithLeadingZeros);
		encryptMessage.setEncryptedText(encryptMessage.getEncryptedText() + cWithLeadingZeros);
	}

}
